package me.smartproxy.tunnel.shadowsocks;

import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.security.MessageDigest;

import me.smartproxy.tunnel.IEncryptor;

public class TableEncryptor implements IEncryptor {

	private byte[] encryptTable = new byte[256];
	private byte[] decryptTable = new byte[256];
    
	public TableEncryptor(String password){
		
		 long a = passwordToInt64(password);
         for (int i = 0; i < 256; i++)
         {
             encryptTable[i] = (byte)i;
         }
         
         System.out.println("mergeSort....");
         long startTime=System.nanoTime();
         encryptTable = mergeSort(encryptTable, a);
         long endTime=System.nanoTime();
         System.out.printf("mergeSort: %3fms\n", (endTime-startTime)/1000D/1000D);
         
         for (int i = 0; i < 256; i++)
         {
             decryptTable[encryptTable[i]&0xFF] = (byte)i;
         }
	}
	
	long passwordToInt64(String password){
		try {
			byte[] passwordBytes=password.getBytes("UTF-8");
			MessageDigest md5 = MessageDigest.getInstance("MD5"); 
			byte[] hashPwd=md5.digest(passwordBytes);
			long a = bytesToInt64(hashPwd);
			return a;
		} catch (Exception e) {
			e.printStackTrace();
			return 0;
		}
	}
 
	long bytesToInt64(byte[] data){
		long value=data[0];
		value|=((long)(data[1]&0xFF)<<8);
		value|=((long)(data[2]&0xFF)<<16);
		value|=((long)(data[3]&0xFF)<<24);
		value|=((long)(data[4]&0xFF)<<32);
		value|=((long)(data[5]&0xFF)<<40);
		value|=((long)(data[6]&0xFF)<<48);
		value|=((long)(data[7]&0xFF)<<56);
		return value;
	}
 
	 private  byte[] mergeSort(byte[] srcArray , long a ){
	   	  byte[] dstArray=new byte[256];
	   	  long a1=Long.MAX_VALUE;
		  long a2=(a&Long.MAX_VALUE);
	   	  int stepSize,leftOffset,leftMaxOffset,rightOffset,rightMaxOffset,dstOffset,leftValue,rightValue;
	   	  
	   	  for (int i = 1; i < 1024; i++) {
             for (stepSize = 1;stepSize < 256;stepSize<<=1){
                 for (dstOffset=0;dstOffset<256;){
	               	  leftOffset=dstOffset;
	               	  leftMaxOffset = leftOffset + stepSize;
	               	  rightOffset=leftMaxOffset;
	                  rightMaxOffset = rightOffset + stepSize;
	                  for (;dstOffset<rightMaxOffset; dstOffset++) {
	                	    if (rightOffset == rightMaxOffset) {
	                       	     dstArray[dstOffset] = srcArray[leftOffset++];
	                        }
	                        else if (leftOffset == leftMaxOffset){
	                       	     dstArray[dstOffset] = srcArray[rightOffset++];
	                        }else {
	                        	leftValue=(srcArray[leftOffset]&0xFF)+i;
		                	    rightValue=(srcArray[rightOffset]&0xFF)+i;
		                	    boolean isLeftSmallThanRight=a>0?((a%leftValue - a%rightValue) <=0):(((a1%leftValue+a2%leftValue+1)%leftValue - (a1%rightValue+a2%rightValue+1)%rightValue)<=0);
		                	    if(isLeftSmallThanRight){
		                	    	dstArray[dstOffset] =  srcArray[leftOffset++];
		                	    }else {
		                	    	dstArray[dstOffset] = srcArray[rightOffset++];
								}
							} 
	                  }   
	              }

                 byte[] temp = dstArray;
                 dstArray = srcArray;
                 srcArray = temp;   
             }
		  }
          return srcArray;
     }
 
	@Override
	public void encrypt(ByteBuffer buffer) {
		byte[] data=buffer.array();
		for (int i = buffer.arrayOffset()+buffer.position(); i < buffer.limit(); i++) {
			data[i]=encryptTable[data[i]&0xFF];
		}
	}

	@Override
	public void decrypt(ByteBuffer buffer) {
		byte[] data=buffer.array();
		for (int i = buffer.arrayOffset()+buffer.position(); i < buffer.limit(); i++) {
			data[i]=decryptTable[data[i]&0xFF];
		}
	}
 
}